package com.thaiopensource.relaxng.output.dtd;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.thaiopensource.relaxng.edit.Combine;
import com.thaiopensource.relaxng.edit.Component;
import com.thaiopensource.relaxng.edit.ComponentVisitor;
import com.thaiopensource.relaxng.edit.Container;
import com.thaiopensource.relaxng.edit.DefineComponent;
import com.thaiopensource.relaxng.edit.DivComponent;
import com.thaiopensource.relaxng.edit.GrammarPattern;
import com.thaiopensource.relaxng.edit.IncludeComponent;
import com.thaiopensource.relaxng.edit.InterleavePattern;
import com.thaiopensource.relaxng.edit.Pattern;
import com.thaiopensource.relaxng.edit.SchemaCollection;
import com.thaiopensource.relaxng.output.common.ErrorReporter;
import com.thaiopensource.util.VoidValue;

class GrammarPart implements ComponentVisitor <VoidValue>
{
  private final ErrorReporter er;
  private final Map <String, Pattern> defines;
  private final Set <String> attlists;
  private final Set <String> implicitlyCombinedDefines;
  private final Map <String, Combine> combineTypes;
  private final SchemaCollection schemas;
  private final Map <String, GrammarPart> parts;
  // maps name to component that provides it
  private final Map <String, Component> whereProvided = new HashMap <String, Component> ();
  private final Set <String> pendingIncludes;

  public static class IncludeLoopException extends RuntimeException
  {
    private final IncludeComponent include;

    public IncludeLoopException (final IncludeComponent include)
    {
      this.include = include;
    }

    public IncludeComponent getInclude ()
    {
      return include;
    }
  }

  GrammarPart (final ErrorReporter er,
               final Map <String, Pattern> defines,
               final Set <String> attlists,
               final SchemaCollection schemas,
               final Map <String, GrammarPart> parts,
               final GrammarPattern p)
  {
    this.er = er;
    this.defines = defines;
    this.attlists = attlists;
    this.schemas = schemas;
    this.parts = parts;
    this.pendingIncludes = new HashSet <String> ();
    this.implicitlyCombinedDefines = new HashSet <String> ();
    this.combineTypes = new HashMap <String, Combine> ();
    visitContainer (p);
  }

  private GrammarPart (final GrammarPart part, final GrammarPattern p)
  {
    er = part.er;
    defines = part.defines;
    schemas = part.schemas;
    parts = part.parts;
    attlists = part.attlists;
    pendingIncludes = part.pendingIncludes;
    implicitlyCombinedDefines = part.implicitlyCombinedDefines;
    combineTypes = part.combineTypes;
    visitContainer (p);
  }

  Set <String> providedSet ()
  {
    return whereProvided.keySet ();
  }

  public VoidValue visitContainer (final Container c)
  {
    final List <Component> list = c.getComponents ();
    for (int i = 0, len = list.size (); i < len; i++)
      (list.get (i)).accept (this);
    return VoidValue.VOID;
  }

  public VoidValue visitDiv (final DivComponent c)
  {
    return visitContainer (c);
  }

  public VoidValue visitDefine (final DefineComponent c)
  {
    final String name = c.getName ();
    final Combine combine = c.getCombine ();
    if (combine == null)
    {
      if (implicitlyCombinedDefines.contains (name))
        er.error ("multiple_no_combine", name, c.getSourceLocation ());
      else
        implicitlyCombinedDefines.add (name);
    }
    else
    {
      final Combine oldCombine = combineTypes.get (name);
      if (oldCombine != null)
      {
        if (oldCombine != combine)
          er.error ("inconsistent_combine", c.getSourceLocation ());
      }
      else
        combineTypes.put (name, combine);
    }
    final Pattern oldDef = defines.get (name);
    if (oldDef != null)
    {
      if (combine == Combine.CHOICE)
        er.error ("sorry_combine_choice", c.getSourceLocation ());
      else
        if (combine == Combine.INTERLEAVE)
        {
          final InterleavePattern ip = new InterleavePattern ();
          ip.getChildren ().add (oldDef);
          ip.getChildren ().add (c.getBody ());
          ip.setSourceLocation (c.getSourceLocation ());
          defines.put (name, ip);
          attlists.add (name);
        }
    }
    else
    {
      defines.put (name, c.getBody ());
      whereProvided.put (name, c);
    }
    return VoidValue.VOID;
  }

  public VoidValue visitInclude (final IncludeComponent c)
  {
    final String uri = c.getUri ();
    if (pendingIncludes.contains (uri))
      throw new IncludeLoopException (c);
    pendingIncludes.add (uri);
    final GrammarPattern p = (GrammarPattern) (schemas.getSchemaDocumentMap ().get (uri)).getPattern ();
    final GrammarPart part = new GrammarPart (this, p);
    parts.put (uri, part);
    for (final String name : part.providedSet ())
      whereProvided.put (name, c);
    pendingIncludes.remove (uri);
    return VoidValue.VOID;
  }

  Component getWhereProvided (final String paramEntityName)
  {
    return whereProvided.get (paramEntityName);
  }
}
